# --------------------------------------------------------------------------------------------------------------------
# Project:             Digital Payment System (DPS)
# Website:             http://code.google.com/p/dps-x509/
# Purpose of document: This class represents the attribute and value fields within the x509 certificates
# --------------------------------------------------------------------------------------------------------------------
import sys, string, logging

sys.path.append("..")

from pyasn1.type import univ

#we would prefer the try to pass, but the import stuff isn't working
try:
  import keyczar.keys as keys
except ImportError:
  from keyczar import *
  
import dps.asn1CertificateSpecification
import dps.errors
import dps.utils
import dps.x509Helper

STRING_MAP = ['teletexString', 'printableString', 'universalString', 'utf8String', 'bmpString', 'ia5String']

# --------------------------------------------------------------------------------------------------------------------
def getOidMap():
  """
  @return: dict from oid to display name
  @rtype:  dict 
  """
  return {
"2.5.4.0": "objectClass",
"2.5.4.1": "aliasedEntryName",
"2.5.4.10": "organizationName",
"2.5.4.10.1": "collectiveOrganizationName",
"2.5.4.11": "organizationalUnitName",
"2.5.4.11.1": "collectiveOrganizationalUnitName",
"2.5.4.12": "title",
"2.5.4.13": "description",
"2.5.4.14": "searchGuide",
"2.5.4.15": "businessCategory",
"2.5.4.16": "postalAddress",
"2.5.4.16.1": "collectivePostalAddress",
"2.5.4.17": "postalCode",
"2.5.4.17.1": "collectivePostalCode",
"2.5.4.18": "postOfficeBox",
"2.5.4.18.1": "collectivePostOfficeBox",
"2.5.4.19": "physicalDeliveryOfficeName",
"2.5.4.19.1": "collectivePhysicalDeliveryOfficeName",
"2.5.4.2": "knowledgeInformation",
"2.5.4.20": "telephoneNumber",
"2.5.4.20.1": "collectiveTelephoneNumber",
"2.5.4.21": "telexNumber",
"2.5.4.21.1": "collectiveTelexNumber",
"2.5.4.22.1": "collectiveTeletexTerminalIdentifier",
"2.5.4.23": "facsimileTelephoneNumber",
"2.5.4.23.1": "collectiveFacsimileTelephoneNumber",
"2.5.4.25": "internationalISDNNumber",
"2.5.4.25.1": "collectiveInternationalISDNNumber",
"2.5.4.26": "registeredAddress",
"2.5.4.27": "destinationIndicator",
"2.5.4.28": "preferredDeliveryMehtod",
"2.5.4.29": "presentationAddress",
"2.5.4.3": "commonName",
"2.5.4.31": "member",
"2.5.4.32": "owner",
"2.5.4.33": "roleOccupant",
"2.5.4.34": "seeAlso",
"2.5.4.35": "userPassword",
"2.5.4.36": "userCertificate",
"2.5.4.37": "caCertificate",
"2.5.4.38": "authorityRevocationList",
"2.5.4.39": "certificateRevocationList",
"2.5.4.4": "surname",
"2.5.4.40": "crossCertificatePair",
"2.5.4.41": "name",
"2.5.4.42": "givenName",
"2.5.4.43": "initials",
"2.5.4.44": "generationQualifier",
"2.5.4.45": "uniqueIdentifier",
"2.5.4.46": "dnQualifier",
"2.5.4.47": "enhancedSearchGuide",
"2.5.4.48": "protocolInformation",
"2.5.4.49": "distinguishedName",
"2.5.4.5": "serialNumber",
"2.5.4.50": "uniqueMember",
"2.5.4.51": "houseIdentifier",
"2.5.4.52": "supportedAlgorithms",
"2.5.4.53": "deltaRevocationList",
"2.5.4.55": "clearance",
"2.5.4.58": "crossCertificatePair",
"2.5.4.6": "countryName",
"2.5.4.7": "localityName",
"2.5.4.7.1": "collectiveLocalityName",
"2.5.4.8": "stateOrProvinceName",
"2.5.4.8.1": "collectiveStateOrProvinceName",
"2.5.4.9": "streetAddress",
"2.5.4.9.1": "collectiveStreetAddress",
"1.2.840.113549.1.9.1": "Email",
"2.5.29.17": "URL"
          }

# --------------------------------------------------------------------------------------------------------------------
def indexToStringType(index):
  """ 
  converts an index to its string type
  @param   index: index in STRING_MAP
  @type    index: int
  @raise exceptions.AssertionError: if index is not in range
  @return: string representation of string type
  @rtype:  string
  """
  global STRING_MAP
  dps.utils.assertion(index in range(len(STRING_MAP)), "x509AttributeAndValue.indexToStringType: index not in range")
  return STRING_MAP[index]

# --------------------------------------------------------------------------------------------------------------------
def stringTypeToIndex(stringType):
  """ 
  converts a string to its required index for asn1. Refer to dps.asn1CertificateSpecification.DirectoryString. 
  defaults to printableString's index if not found
  @param  stringType: string representation of string type
  @type   stringType: string
  @rtype: int
  """
  global STRING_MAP
  ucStringType = stringType.upper()
  index = 1 # printable string
  for i in range(len(STRING_MAP)):
    if STRING_MAP[i].upper() == ucStringType:
      index = i
      break
  dps.utils.out("x509AttributeAndValue.stringTypeToIndex: string type: %s index: %d" % (stringType, index), 1)
  return index

# --------------------------------------------------------------------------------------------------------------------
def canFindOid(oid):
  """ 
  can find Oid string
  @param  oid: dot delimited string representation
  @type   oid: string 
  @rtype: bool
  """
  return getOidMap().has_key(oid)

# --------------------------------------------------------------------------------------------------------------------
def oidToStringRepresentation(oid):
  """ 
  converts a key (eg. 2.5.4.7) to its string representation 
  @param   oid: dot delimited string representation
  @type    oid: string 
  @return: pretty print or oid if not found
  @rtype:  string
  """
  ret = oid
  ismatch = canFindOid(oid)
  if ismatch:
    ret = getOidMap()[oid]
  dps.utils.out("x509AttributeAndValue.oidToStringRepresentation: oid: %s str: %s isMatch: %s" % (oid, ret, ismatch), 1)
  return ret

# --------------------------------------------------------------------------------------------------------------------
def stringRepresentationToOid(s):
  """ 
  converts a string to its key (eg. 2.5.4.7) 
  @param   s: pretty print
  @type    s: string
  @return: dot delimited string representation
  @rtype:  string 
  """
  oid = s
  oids = getOidMap()
  ucs = s.upper()
  for k in oids.keys():
    if oids[k].upper() == ucs:
      oid = k
      break
  dps.utils.out("x509AttributeAndValue.stringRepresentationToOid: str: %s oid: %s" % (s, str(oid)), 1)        
  return oid


# --------------------------------------------------------------------------------------------------------------------
class X509AttributeAndValue:
  """ This class represents the attribute and value fields within the x509 certificates """ 

# --------------------------------------------------------------------------------------------------------------------
  def __init__(self, value, oid= stringRepresentationToOid("commonName"), valueType= "printableString"):
    """ constructor 
    @param value: string value
    @type  value: string
    @param oid: ie. 2.5.4.7. http://en.wikipedia.org/wiki/Object_identifier
    @type  oid: string
    @param valueType: string value
    @type  valueType: string
    """    
    self.__oid_ = oid
    self.__value_ = value
    self.__valueType_ = valueType
  
# --------------------------------------------------------------------------------------------------------------------
  @staticmethod  
  def loadFromAsn1(asn1Obj):
    """ create from an asn1 object
    @param  asn1Obj: certificate of type
    @type   asn1Obj: dps.asn1CertificateSpecification.AttributeTypeAndValue
    @rtype: X509AttributeAndValue
    """
    dps.utils.assertion(isinstance(asn1Obj, dps.asn1CertificateSpecification.AttributeTypeAndValue), \
                        "X509AttributeAndValue.loadFromAsn1: Invalid asn1Obj")
    typeAsn1 = asn1Obj.getComponentByName("type")
    oid = ""
    for part in typeAsn1:
      oid += str(part) + "."
    oid = oid[:-1]

    #value stuff
    valueAsn1 = asn1Obj.getComponentByName("value")
    valueType = valueAsn1.getName()
    value = valueAsn1.getComponent()
    
    return X509AttributeAndValue(value, oid, valueType)
    
# --------------------------------------------------------------------------------------------------------------------
  def asAsn1(self):
    """ 
    @rtype: asn1
    """
    attributeTypeAndValue = dps.asn1CertificateSpecification.AttributeTypeAndValue()
    attributeType = dps.asn1CertificateSpecification.AttributeType(self.__oid_)
    attributeValue = dps.asn1CertificateSpecification.AttributeValue()
    attributeValue.setComponentByPosition(stringTypeToIndex(self.__valueType_), self.__value_)
    attributeTypeAndValue.setComponentByPosition(0, attributeType)
    attributeTypeAndValue.setComponentByPosition(1, attributeValue)    
    return attributeTypeAndValue

# --------------------------------------------------------------------------------------------------------------------
  def getOidStringRepresentation(self):
    """ 
    @return: object as type 
    @rtype:  string
    """
    return oidToStringRepresentation(self.__oid_)

# --------------------------------------------------------------------------------------------------------------------
  def getOid(self):
    """ 
    @return: object as type 
    @rtype:  string
    """
    return self.__oid_

# --------------------------------------------------------------------------------------------------------------------
  def getValue(self):
    """ 
    @return: object as asn1 
    @rtype:  string
    """
    return self.__value_
  
# --------------------------------------------------------------------------------------------------------------------
  def __str__(self):
    """ as string """
    return "%s:%s" % (self.getOidStringRepresentation(), self.__value_)
    
  
# --------------------------------------------------------------------------------------------------------------------
# --------------------------------------------------------------------------------------------------------------------
class X509AttributeAndValueList:
  """ array of X509AttributeAndValues """
  
# --------------------------------------------------------------------------------------------------------------------
  def __init__(self):
    """ constructor """
    self.list_ = []

# --------------------------------------------------------------------------------------------------------------------
  @staticmethod  
  def loadFromAsn1(asn1Obj):
    """ create from an asn1 object
    @param  asn1Obj: certificate component
    @type   asn1Obj: dps.asn1CertificateSpecification.AttributeTypeAndValue 
    @rtype: X509AttributeAndValueList
    """

    dps.utils.assertion(isinstance(asn1Obj, dps.asn1CertificateSpecification.Name), \
                        "X509AttributeAndValueList.loadFromAsn1: Invalid asn1Obj")
    avlist = X509AttributeAndValueList()
    try:
      seqAsn1 = asn1Obj.getComponentByName("")
      for s in seqAsn1:
        av = X509AttributeAndValue.loadFromAsn1(s.getComponentByPosition(0))
        avlist.addItem(av)
        print str(av)
    except dps.errors.DPSBaseError:
      pass #raise?
    except Exception, e:
      dps.utils.out("Exception in X509AttributeAndValueList.loadFromAsn1: " + str(sys.exc_info()[0]) + str(e))
    
    return avlist
  
# --------------------------------------------------------------------------------------------------------------------
  def addItem(self, asn1Obj):
    """ add item to the list
    @param asn1Obj: item to add to list
    @type  asn1Obj: dps.x509AttributeAndValue.X509AttributeAndValue 
    """
    dps.utils.assertion(isinstance(asn1Obj, X509AttributeAndValue), \
                        "X509AttributeAndValueList.addItem: Invalid asn1Obj")
    self.list_.append(asn1Obj)

# --------------------------------------------------------------------------------------------------------------------
  def asAsn1(self):
    """ 
    returns the list as an asn1 type 
    @rtype: dps.asn1CertificateSpecification.Name
    """
    name = dps.asn1CertificateSpecification.Name()
    seq = dps.asn1CertificateSpecification.RDNSequence()
    
    for i in range(len(self.list_)):
      rdn = dps.asn1CertificateSpecification.RelativeDistinguishedName()
      rdn.setComponentByPosition(0, self.list_[i].asAsn1()) 
      seq.setComponentByPosition(i, rdn)
    
    name.setComponentByPosition(0, seq)

    return name

# --------------------------------------------------------------------------------------------------------------------
  def __str__(self):
    """ as string """
    ret = ""
    for l in self.list_:
      ret += str(l) + " "
    ret = ret[:-1]
    return ret
  